<?php
namespace Hilaoyu\Utils;

use Hilaoyu\Utils\Traits\ErrorsTrait;

/**
 * Created by laoyu.
 * User: hilaoyu@qq.com
 */


class UtilProxyClient {
    use ErrorsTrait;
    protected $fsock = null;
    protected $proxy_type = '';//http  socks5
    static public function makeHttpProxy(string $host,int $port=8080,string $username='',string $password='',int $timeout=10){
        $_this = new static();
        $_this->httpProxy($host,$port,$username,$password,$timeout);
        return $_this;
    }
    static public function makeSocks5Proxy(string $host,int $port=8080,string $username='',string $password='',int $timeout=10){
        $_this = new static();
        $_this->socks5Proxy($host,$port,$username,$password,$timeout);
        return $_this;
    }

    public function httpProxy(string $host,int $port=8080,string $username='',string $password='',int $timeout=10){

        try{
            $fsock = fsockopen($host, $port, $errno, $errstr, $timeout);
            if (!$fsock) {
                throw new \Exception($errstr);
            }

            if($username) {
                fputs($fsock, "AUTHORIZATION: Basic " . base64_encode($username.':'.$password) . "\r\n");
                while ($line = fgets($fsock, 1024)) {
                    if ($line == "\r\n") {
                        break;
                    }

                }
            }

            $this->fsock = $fsock;
            $this->proxy_type='http';
        }catch (\Exception $exception){
            $this->setError($exception->getMessage());
            $this->fsock = null;
        }


        return $this;
    }
    public function socks5Proxy(string $host,int $port=8080,string $username='',string $password='',int $timeout=10){


        try{
            // SOCKS5 connection info
            $fsock = fsockopen($host, $port, $errno, $errstr, 1);
            if (!$fsock) {
                throw new \Exception('socks服务器连接失败'.$errstr);
            }



            if($username){
                $request = pack("C3", 0x05, 0x01, 0x02);
                if (fwrite($fsock, $request) != strlen($request)) {
                    throw new \Exception('向socks服务器发送密码认证模式失败');
                }
                $response = fread($fsock,512);
                //dump(bin2hex($request),bin2hex($response));
                if($response !== pack('C2',0x05,0x02)){
                    throw new \Exception('socks服务器不支持密码认证模式'.$errno);
                }
                $request = pack('C2',0x01,strlen($username)).$username.pack('C1',strlen($password)).$password;
                if (fwrite($fsock, $request) != strlen($request)) {
                    throw new \Exception('向socks服务器发送账号密码');
                }
                $response = fread($fsock, 2);
                if($response !==pack('C2',0x01,0x00)){
                    throw new \Exception('socks服务器账号密码认证失败'.$errno);
                }

            }else{
                $request = pack("C3", 0x05, 0x01, 0x00);
                if (fwrite($fsock, $request) != strlen($request)) {
                    throw new \Exception('向socks服务器发送无认证模式失败');
                }
                $response = fread($fsock, 2);
                //dump(bin2hex($request),bin2hex($response));
                if (substr($response,0,2) !== pack('C2',0x05,0x00)) {
                    throw new \Exception('socks服务器需要认证'.$errno);
                }
            }


            $this->fsock = $fsock;
            $this->proxy_type='socks5';
        }catch (\Exception $exception){
            $this->setError($exception->getMessage());
            $this->fsock = null;
        }
        return $this;
    }

    public function proxyTo($host,$port){
        if(!$this->fsock){
            $this->setError('proxy 未连接');
            return $this;
        }
        try{
            switch ($this->proxy_type){
                case 'http':
                    fputs($this->fsock, 'CONNECT '.$host.':'.$port." HTTP/1.0\r\n");
                    fputs($this->fsock, "\r\n");
                    while ($line = fgets($this->fsock, 1024)) {
                        dump($line);
                        if ($line == "\r\n") {
                            break;
                        }
                    }
                    break;

                case 'socks5':
                    $port = pack('n', $port);
                    //$port = chr($port >> 8).chr($port & 255);
                    $host = chr(strlen($host)) . $host;
                    //$host = implode('',array_map('chr',explode('.',$host)));
                    $request = pack("C4", 0x05, 0x01, 0x00,0x03).$host.$port;

                    if (fwrite($this->fsock, $request) != strlen($request)) {
                        throw new \Exception('向socks服务器发送连接请求失败');
                    }
                    $response = fread($this->fsock, strlen($host) + 6);
                    if (substr($response, 0, 2) != pack('C2',0x05,0x00)) {
                        //echo bin2hex($response) . "\n";
                        throw new \Exception("socks服务器不支持此项代理");
                    }
                    break;

                default:
                    break;
            }
        }catch (\Exception $exception){
            $this->setError($exception->getMessage());
            $this->fsock = null;
        }


        return $this;
    }

    public function getFsock(){
        return $this->fsock;
    }

    public function disconnect(){
        if($this->fsock){
            @fclose($this->fsock);
        }
    }

    public function __destruct(){
        $this->disconnect();
    }



}
